package org.wsff.tools.api.gateway.core;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.wsff.tools.api.gateway.error.GatewayException;
import org.wsff.tools.api.gateway.error.ThrowableAnalyzer;
import org.wsff.tools.api.gateway.error.ValidationException;
import org.wsff.tools.api.gateway.manager.ProxyManager;
import org.wsff.tools.api.gateway.manager.ProxyWrapper;

/**
 * 网关抽象类
 *
 * @author ryan
 * @version Id: AbstractGateway.java, v 0.1 2022-01-18 18:01 ryan Exp $$
 */
@Slf4j
public abstract class AbstractGateway<Request extends Serializable, Response extends Serializable> implements Gateway<Request, Response> {

    /** The Throwable analyzer */
    private static final ThrowableAnalyzer   THROWABLE_ANALYZER = new ThrowableAnalyzer();

    /** The proxy invoker provider */
    @Setter
    @Resource
    private ProxyManager<Request, Response> proxyManager;

    /**
     * API服务入口，根据服务方法，调度具体的业务接口执行业务逻辑
     *
     * @param request API request
     * @param name  proxy name 
     * @return API response
     * @throws GatewayException 网关异常
     */
    @Override
    public Response invoke(Request request, String name) throws GatewayException {
        baseValidate(request, name);
        GatewayContext<Request, Response> context = getContext(request, name);
        return invoke(context);
    }

    /**
     * 执行
     *
     * @param context 上下文
     * @return 响应
     * @throws GatewayException 异常
     */
    protected Response invoke(GatewayContext<Request, Response> context) throws GatewayException {
        try {
            // 1.proxy biz validator
            validate(context.getRequest(), context.getName());

            // 2.proxy pre filter
            preFilter(context);

            // 3.proxy execute biz
            Response response = execute(context);
            context.setResponse(response);

            // 4.proxy post filter
            postFilter(context);
            return response;
        } catch (GatewayException e) {
            throw resolveError(context, "gateway error: " + e.getMessage(), e);
        } catch (Exception e) {
            throw resolveError(context, "unknown error: " + e.getMessage(), e);
        } finally {
            // 5.proxy final filter
            finallyFilter(context);
        }
    }

    /**
     * 基础校验
     *
     * @param request 业务请求
     * @param name proxy name
     */
    protected void baseValidate(Request request, String name) throws ValidationException {
        if (StringUtils.isBlank(name)) {
            throw new ValidationException("ProxyEnum cannot be null");
        }
        if (request == null) {
            throw new ValidationException("Gateway service request cannot be null");
        }
    }

    /**
     * Resolve error gateway exception.
     *
     * @param context the context
     * @param message the message
     * @param e       the e
     * @return  the gateway exception
     */
    protected GatewayException resolveError(GatewayContext<Request, Response> context, String message, Exception e) {
        Throwable[] causeChain = THROWABLE_ANALYZER.determineCauseChain(e);
        GatewayException lye = (GatewayException) THROWABLE_ANALYZER.getFirstThrowableOfType(GatewayException.class, causeChain);
        if (lye != null) {
            return lye;
        }
        String code = "-1";
        String msg;
        if (StringUtils.isNotBlank(e.getMessage())) {
            msg = e.getMessage();
        } else {
            msg = "system error, error type: " + message + e.getClass().getName();
        }
        return GatewayException.create(code, msg, e);
    }

    /**
     * 构建网关上下文
     *
     * @param request request
     * @param name proxy name
     * @return context
     */
    protected abstract GatewayContext<Request, Response> buildGatewayContext(Request request, String name);

    /**
     * proxy service execute
     *
     * @param context context
     * @return response
     */
    private Response execute(GatewayContext<Request, Response> context) throws GatewayException {
        ProxyWrapper<Request, Response> invoker = proxyManager.getInvoker(context.getName());
        if (invoker == null) {
            throw GatewayException.create("proxy invoker named:" + context.getName() + " is not existed");
        }
        if (invoker.isOffline()){
            throw GatewayException.create("proxy invoker named:" + context.getName() + " is offline");
        }
        return invoker.get().invoke(context);
    }

    /**
     * proxy filter
     *
     * @param context context
     */
    private void finallyFilter(GatewayContext<Request, Response> context) {
        doFilter(context, ProxyFilter.FilterType.FINAL.name());
    }

    /**
     * post proxy filter
     * 
     * @param context context
     */
    private void postFilter(GatewayContext<Request, Response> context) {
        doFilter(context, ProxyFilter.FilterType.POST.name());
    }

    /**
     * pre proxy filter
     * 
     * @param context context
     */
    private void preFilter(GatewayContext<Request, Response> context) {
        doFilter(context, ProxyFilter.FilterType.PRE.name());
    }

    /**
     * 执行过滤器
     * 
     * @param context context
     * @param name proxy name
     */
    private void doFilter(GatewayContext<Request, Response> context, String name) {
        ProxyFilter<Request, Response> filter = proxyManager.getFilterByType(context.getName(), name);
        if (filter != null) {
            filter.filter(context);
        }
    }

    /**
     * biz validators
     *
     * @param request request
     * @param name proxy name
     */
    private void validate(Request request, String name) throws GatewayException {
        ProxyValidator<Request> validator = proxyManager.getValidator(name);
        if (validator != null) {
            validator.validate(request);
        }
    }

    /**
     * Gets context.
     *
     * @param request the request
     * @param  name  the proxy name 
     * @return the context
     */
    private GatewayContext<Request, Response> getContext(Request request, String name) {
        return buildGatewayContext(request, name);
    }

}
